package se.cth.hedgehogphoto.map.model;
import java.awt.Dimension;
import java.awt.Point;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import se.cth.hedgehogphoto.database.Files;
import se.cth.hedgehogphoto.database.LocationObject;
import se.cth.hedgehogphoto.database.PictureObject;
import se.cth.hedgehogphoto.log.Log;
/**
* The model-warpper-class in the map-subsystem.
* @author Florian Minges
*/
public class MapModel extends Observable implements Observer, PropertyChangeListener {
private List<AbstractMarkerModel> markerModels;
private MapPanel map;
private Files files;
private int width;
private int height;
/* Listen to parent container size changes? */
public MapModel(Files files) {
this.files = files;
this.files.addObserver(this);
initialize();
}
/**
* Fetches pictures, and assigns a marker to each one of it.
* Creates the map, calibrates it to show all markers and
* then sets the relative pixel position of each marker.
* Overlapping markers combine to "multiple"-markers,
* ie no overlapping markers will ever be shown.
*/
protected void initialize() {
this.markerModels = new LinkedList<AbstractMarkerModel>();
List<PictureObject> pictures = this.files.getPictureList(); //fetch pictures
List<LocationObject> locations = new LinkedList<LocationObject>();
int nbrOfPictures = pictures.size();
Log.getLogger().log(Level.INFO, nbrOfPictures + " pictures were found in the Files-class.");
int index;
for (index = 0; index < nbrOfPictures; index++) {
PictureObject picture = pictures.get(index);
LocationObject location = picture.getLocation();
if (location != null) {
if (location.validPosition()) {
this.markerModels.add(new MarkerModel(picture));
locations.add(picture.getLocation());
Log.getLogger().info(location.getLocation() + "\n" + location.getLongitude() + ", " + location.getLatitude() + "\n");
Log.getLogger().log(Level.INFO, "en location!");
}
}
}
calibrateMap(locations); //make every location fit on the screen
int nbrOfMarkers = this.markerModels.size();
for (index = 0; index < nbrOfMarkers; index++) {
LocationObject location = locations.get(index);
Point pixelPosition = getMapPanel().getPixelPosition(location);
this.markerModels.get(index).setPointerPosition(pixelPosition);
}
do {
clearChanged();
organizeMarkers();
} while (hasChanged());
setChanged();
notifyObservers(Global.MARKERS_UPDATE);
}
/**
* Goes through all the markers and merges intersecting ones
* into MultipleMarkerModels.
*/
public void organizeMarkers() {
for (int index = 0; index < this.markerModels.size() - 1; index++) {
AbstractMarkerModel marker = this.markerModels.get(index);
int nbrOfMarkers = this.markerModels.size();
for (int indexToCheckAgainst = index + 1; indexToCheckAgainst < nbrOfMarkers; indexToCheckAgainst++) {
AbstractMarkerModel markerTwo = this.markerModels.get(indexToCheckAgainst);
if (marker.intersects(markerTwo)) {
this.markerModels.add(new MultipleMarkerModel(marker, markerTwo));
this.markerModels.remove(markerTwo);
this.markerModels.remove(marker);
index--;
setChanged();
break;
}
}
}
}
public void calibrateMap(List<LocationObject> locations) {
getMapPanel().calibrate(locations);
getMapPanel().addPropertyChangeListener(this);
}
/** Returns the map, OR if it doesn't exist yet, creates it. */
public MapPanel getMapPanel() {
if (map == null) {
map = new MapPanel();
}
return map;
}
public List<AbstractMarkerModel> getMarkerModels() {
return this.markerModels;
}
public Dimension getSize() {
return new Dimension(getWidth(), getHeight());
}
public int getWidth() {
return this.width;
}
public int getHeight() {
return this.height;
}
@Override
public void update(Observable o, Object arg) {
getMapPanel().removePropertyChangeListener(this); //remove listener during calibration
removeMarkerModelListeners();
initialize();
setChanged();
notifyObservers(Global.FILES_UPDATE);
}
/**
* IMPORTANT: We want the markers to update themself,
* and after that we want to check if there are any overlapping
* markers, and combine them. So that's why this class listens
* to the mapPanel and sends the event on to the markers.
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
for (AbstractMarkerModel model: this.markerModels) {
model.propertyChange(event);
}
String property = event.getPropertyName();
if (property.startsWith(Global.ZOOM)) {
do {
this.clearChanged();
organizeMarkers();
} while (hasChanged());
setChanged();
notifyObservers(Global.MARKERS_UPDATE);
}
}
public void removeMarkerModelListeners() {
for (AbstractMarkerModel marker: markerModels) {
marker.deleteObservers();
}
}
}